package com.example.zjx8969.myapplication

import android.net.Uri
import android.util.ArrayMap
import android.util.Log
import androidx.annotation.Keep
import androidx.lifecycle.Lifecycle
import com.google.gson.JsonElement
import kotlinx.coroutines.*
import okhttp3.*
import java.io.IOException
import java.lang.ref.WeakReference
import java.lang.reflect.Type
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlin.properties.Delegates

class HttpRequest constructor(private val baseUrl: String) {
    companion object {
        @JvmStatic
        var defaultPostHttpConstructor: ((apiCode: Int, builder: FormBody.Builder, map: ArrayMap<String, String>) -> Unit) by Delegates.notNull()

        @JvmStatic
        var httpClient: OkHttpClient by Delegates.notNull()
    }

    private val url: String
        get() {
            return if (isPost || parameter.isEmpty()) baseUrl else {
                val builder = Uri.parse(baseUrl).buildUpon()
                for ((key, value) in parameter) {
                    builder.appendQueryParameter(key, value)
                }
                builder.build().toString()
            }
        }

    constructor(baseUrl: String, apiCode: Int) : this(baseUrl) {
        this.apiCode = apiCode
    }

    var apiCode: Int = -1
    private var parameter: ArrayMap<String, String> = ArrayMap(8)
    var tag: String? = null
    private var postHttpConstructor: ((builder: FormBody.Builder, map: ArrayMap<String, String>) -> Unit)? = null
    private var lifecycleWeakReference: WeakReference<Lifecycle>? = null
    var isPost = false
    var switchToMainThread = false

    var type: Type? = null
    private var coroutineScope: CoroutineScope? = null
    fun coroutineScope(coroutineScope: CoroutineScope): HttpRequest {
        this.coroutineScope = coroutineScope
        return this
    }

    fun lifecycle(lifecycle: Lifecycle): HttpRequest {
        lifecycleWeakReference = WeakReference(lifecycle)
        return this
    }

    fun addParameter(key: String, value: String): HttpRequest {
        parameter[key] = value
        return this
    }

    fun addParameterIfTrue(boolean: Boolean, key: String, value: String): HttpRequest {
        if (boolean) parameter[key] = value
        return this
    }

    fun addParameterIfTrue(key: String, value: String, predicate: () -> Boolean): HttpRequest {
        if (predicate()) parameter[key] = value
        return this
    }

    fun customConstructHttpRequestBody(body: (builder: FormBody.Builder, map: ArrayMap<String, String>) -> Unit): HttpRequest {
        postHttpConstructor = body
        return this
    }

    fun <T> execute(onFailure: (errorCode: Int, msg: String?) -> Unit, onSuccess: (t: T) -> Unit) {
        val coroutine = if (coroutineScope != null) coroutineScope else GlobalScope
        coroutine?.launch(Dispatchers.IO) {
            try {
                val requestBuilder = Request.Builder().tag(tag).url(url)
                if (isPost) {
                    val builder = FormBody.Builder()
                    when {
                        apiCode != -1 -> {
                            defaultPostHttpConstructor(apiCode, builder, parameter)
                            postHttpConstructor?.let {
                                it(builder, parameter)
                            }
                        }
                        postHttpConstructor == null -> {
                            parameter.forEach {
                                builder.add(it.key, it.value)
                            }
                        }
                        else -> {
                            postHttpConstructor?.let { it(builder, parameter) }
                        }
                    }
                    requestBuilder.post(builder.build())
                } else {
                    requestBuilder.get()
                }

                val resultString: String? = httpClient.newCall(requestBuilder.build()).await()
                if (resultString.isNullOrBlank()) {
                    postFailure(switchToMainThread, -1, "empty result", onFailure)
                    return@launch
                }
                Log.d("tag", "${Thread.currentThread().name}, $resultString")
                if (apiCode == -1) {
                    convert2Type(resultString)
                } else {
                    val dataBean = GsonAgent.getInstance().gson.fromJson(resultString, DataBean::class.java)
                    if (dataBean.code != 0) {
                        postFailure(switchToMainThread, dataBean.code, dataBean.message, onFailure)
                        null
                    } else {
                        convert2Type(dataBean.data.toString())
                    }
                }?.let {
                    postSuccess(switchToMainThread, onSuccess, it)
                }

            } catch (e: Exception) {
                e.printStackTrace()
                postFailure(switchToMainThread, -1, e.message, onFailure)
            }
        }

    }

    private fun convert2Type(content: String): Any {
        return when (type) {
            null, String::javaClass, String::class.java -> content
            else ->
                GsonAgent.getInstance().gson.fromJson<Any>(content, type)
        }
    }

    @Suppress("unchecked_cast")
    private inline fun <T> CoroutineScope.postSuccess(switchToMainThread: Boolean, crossinline onSuccess: (t: T) -> Unit, t: Any) {
        if (switchToMainThread) {
            launch(Dispatchers.Main) {
                onSuccess(t as T)
            }
        } else {
            onSuccess(t as T)
        }

    }

    @Keep
    private data class DataBean(val code: Int, val message: String, val data: JsonElement)

    private inline fun CoroutineScope.postFailure(switchToMainThread: Boolean, errorCode: Int = -1, msg: String? = null, crossinline onFailure: (errorCode: Int, msg: String?) -> Unit) {
        if (switchToMainThread) {
            launch(Dispatchers.Main) {
                onFailure(errorCode, msg)
            }
        } else {
            onFailure(errorCode, msg)
        }

    }

    private suspend fun Call.await(): String? = suspendCoroutine { continuation ->

        enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                continuation.resumeWithException(e)
            }

            override fun onResponse(call: Call, response: Response) {
                if (response.isSuccessful) {
                    continuation.resume(response.body?.string())
                } else {
                    continuation.resumeWithException(IllegalArgumentException("code=$response"))
                }
            }

        })
    }
}

suspend fun Call.await(): Response? = suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
        continuation.resumeWithException(Exception(it))
    }
    enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            continuation.resumeWithException(e)
        }

        override fun onResponse(call: Call, response: Response) {
            continuation.resume(response)
        }

    })
}

